/**
 * 测试用例excel表格导出 614
 *
 * Created by nongzhizhong on 2021/12/07
 */
const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const { TestCaseTransformer, IntegrationTestCaseTransformer } = require('smart-rocket-unit');
const BaseTestCasesExporter = require('../base-exporter');
const common = require('../../../common');
const testModel = require('../../../tests');
const testGroupModel = require('../../../test-case-groups');
const utility = require('../../../../models/utility');
const Excel = require('../../report/adapter/excel');
const DataReport = require('./data-adapter');
const handleIntegrationVariables = require('../../../../models/handle-integration-variables');

class ExcelTestCasesExporterOf614 extends BaseTestCasesExporter {
  constructor(versionId, locale, version = null, project = null, creator = null, fileIds = null) {
    super(versionId, locale, version, project, creator, fileIds);
    this.setDataAdapter(new DataReport());
    this.exportUserDefinedFields = ['等价类', '边界值', '重复性', '特殊路径', 'MCDC', '循环验证', '溢出验证', '除零验证', '用例说明'];
  }

  async exportTestCases(progressIndicator) {
    const result = {
      cachePath: this.outputDir,
    };
    await this.spliteByFunction(null, progressIndicator);
    const fileName = `${this.project.projectName}-${this.version.versionName}.test-cases-614.xlsx.zip`;
    result.testsPath = path.join(this.outputDir, fileName);
    const testsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(testsDir, result.testsPath);
    return result;
  }

  /**
   * 按文件分割
   * @return {Promise}
   */
  async spliteByFile(callback = null, progressIndicator) {
    const result = {};
    const { projectDir, versionPath, versionType } = await this.spliteByProjectVersion((versionDir) => fs.ensureDir(versionDir));
    result.projectDir = projectDir;
    result.versionPath = versionPath;
    result.versionDir = versionPath;
    result.versionType = versionType;
    await this.filterSelectedFiles();
    await this.createVersionInfoFile(result.projectDir, result.versionType, 'xlsx');
    await this.handleFiles(this.fileIds, result.versionDir, callback, progressIndicator);
    return result;
  }

  /**
 * 按函数拆分测试用例
 * @param {object} func 函数信息
 * @param {object} file 文件信息
 * @param {object} typeSystem 类型系统
 * @param {string} versionDir 版本路径
 * @returns
 */
  async handleFunction(func, file, typeSystem, versionDir) {
    if (!file.functions || (file.functions.length > 0
      && !file.functions.includes(func._id.toString()))) {
      return Promise.resolve();
    }
    const { functionName } = func;
    const testsPath = `${path.join(versionDir, file.path, `test_${functionName}`)}.xlsx`;
    await super.handleFunction(func, file, typeSystem, versionDir);
    return this.exportFunctionTestCase(
      testsPath,
      typeSystem,
      {
        tests: func.tests,
        functionName: func.functionName,
        variables: func.variables,
        functionId: func._id,
        fileId: func.fileId,
      }
    );
  }

  buildVariableType(resVariableArray, variableName, variable, type, attribute) {
    // 处理自定义全局变量数组类型
    if (!variable['@type'] && variable['@length'] && variable['@elementType'] && type[variable['@elementType']]) {
      const newArrayType = `${variable['@elementType']} [${variable['@length']}]`;
      variable['@type'] = newArrayType;
      type[newArrayType] = {
        '@attributes': [
          'isArray',
        ],
        '@canonical': newArrayType,
        '@elementType': variable['@elementType'],
        '@length': `${variable['@length']}`,
        '@unqualified': newArrayType,
      };
    }
    const variableType = type[variable['@type']];
    if (!variableType) {
      throw new Error(`Can't find the type(${variable['@type']}) in typeSystem`);
    }
    const canonicalType = type[variableType['@canonical']];
    if (!canonicalType) {
      throw new Error(`Can't find the canonical(${variableType['@canonical']}) in typeSystem`);
    }
    const unqualifiedType = type[canonicalType['@unqualified']];
    if (!unqualifiedType) {
      throw new Error(`Can't find the unqualified(${canonicalType['@unqualified']}) in typeSystem`);
    }
    const attributes = unqualifiedType['@attributes'];
    if (!attributes || attributes.length === 0) {
      throw new Error(`Can't find the attributes(${JSON.stringify(unqualifiedType)})in unqualifiedType`);
    }
    // 去掉指针形参
    if (attributes.includes('isPointer') && attribute === '一般形参') {
      return;
    }
    const isEnumType = attributes.includes('isEnumeral');
    const values = variable['@value'];
    if (utility.isObjectType(values)) {
      Object.keys(values).forEach((index) => {
        if (attributes.includes('isArray')) { // 数组
          values[index]['@type'] = unqualifiedType['@elementType'];
          this.buildVariableType(
            resVariableArray,
            `${variableName}[${parseInt(index, 10)}]`,
            values[index],
            type,
            attribute
          );
        } else if (attributes.includes('isStructure')
        || attributes.includes('isRecord')
        || attributes.includes('isCXXRecord')
        || attributes.includes('isClass')) { // 结构体,联合体，类
          this.buildVariableType(
            resVariableArray,
            `${variableName}.${index}`,
            values[index],
            type,
            attribute
          );
        }
      });
    } else {
      let value = variable['@value'] || '';
      if (isEnumType && value && unqualifiedType['@enums'][value]) {
        value = unqualifiedType['@enums'][value];
      }
      resVariableArray.push({
        name: variableName,
        value,
        type: variable['@type'] || 'void',
        isUsed: variable['@isUsed'] !== undefined ? variable['@isUsed'] : 1,
        attribute,
      });
    }
  }

  handleIntegrationVariableFile(fileId, variableName, testTransformerVariables) {
    let variableFileId = fileId.toString();
    let newName = variableName;
    if (this.isIntegrationTest()) {
      if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(variableName)) {
        [newName, variableFileId] = variableName.split('@');
      } else {
        testTransformerVariables.forEach((variable) => {
          if (variable.name === variableName && variable.fileId) {
            variableFileId = variable.fileId;
          }
        });
      }
    }
    return { variableFileId, newName };
  }

  /**
   *  处理导出到用例格式
   * @param {object} testCaseStructuralData 需要输出的数据
   * @param {object} testCase 原始测试用例
   * @param {object} types 文件类型系统
   * @returns
   */
  buildTestCaseExcelData(testCaseStructuralData, testCase, variables, types, testTransformer, funcDisplaySetting) {
    const {
      data, userDefinedFields, description, groupNumber, sortNumber,
    } = testCase;
    const { globalVariablesData, paramVariablesData, globalOutputData } = testTransformer;
    const { fileId } = testCaseStructuralData;
    const { functionName, tableHeader, testCases } = testCaseStructuralData;
    let CaseID = testCase.identifier;
    const variableStubs = variables.stubs || {};
    const exportTestCase = {
      input: { ReqID: '' },
      output: {},
      userDefinedFields: [],
      sortNumber,
      groupNumber,
    };
    userDefinedFields.forEach((udf) => {
      if (udf.name === 'CaseID' && udf.value) {
        CaseID = udf.value;
      }
      if (udf.name === 'ReqID' && udf.value) {
        exportTestCase.input.ReqID = udf.value;
      }
    });
    // 给用例自定义字段填值
    for (let i = 0; i < this.exportUserDefinedFields.length; i += 1) {
      const definedName = this.exportUserDefinedFields[i];
      let value = '';
      const testCaseUserDefinedField = userDefinedFields.find((u) => u.name === definedName);
      if (testCaseUserDefinedField && ['0', '1'].includes(testCaseUserDefinedField.value)) {
        value = testCaseUserDefinedField.value;
      }
      if (parseInt(value, 10) === 0) {
        value = '×';
      }
      if (parseInt(value, 10) === 1) {
        value = '√';
      }
      if (definedName === '用例说明') {
        value = description;
      }
      exportTestCase.userDefinedFields[i] = value;
    }
    // 输入数据前三个字段，对应表格
    exportTestCase.input.CaseID = CaseID;
    exportTestCase.input.LoopCnt = testCase.execCount;
    let inputGlobal;
    let inputParams;
    const stubs = data.stubs || {};
    let outputGlobal;
    let outputParams;
    let returnVal;
    if (data.variables) {
      inputGlobal = data.variables.global || {};
      inputParams = data.variables.params || {};
    }
    if (data.output) {
      outputGlobal = data.output.global || {};
      outputParams = data.output.params || {};
      returnVal = data.output['%'];
    }
    // 处理桩函数
    Object.keys(stubs).forEach((key) => {
      if (!variableStubs[key]) {
        return;
      }
      const stubName = variableStubs[key].originalName || variableStubs[key]['@name'];
      const stubValues = stubs[key]['@value'] || {};
      const stubValuesType = variableStubs[key]['%'] && variableStubs[key]['%']['@type'] ? variableStubs[key]['%']['@type'] : '';
      const stubExecCount = Object.keys(stubValues).length;
      Object.keys(stubValues).forEach((stubTime) => {
        const stubTimeValue = stubValues[stubTime]['%'] ? stubValues[stubTime]['%']['@value'] : '';
        const stubTimeValueType = stubValues[stubTime]['%'] ? stubValues[stubTime]['%']['@type'] : stubValuesType;
        stubTime = parseInt(stubTime, 10);
        const newStubName = stubExecCount === 1 ? `[I]${stubName}` : `[I]${stubName}[${stubTime + 1}]`;
        if (!tableHeader.input[newStubName]) {
          tableHeader.input[newStubName] = { name: newStubName, type: stubTimeValueType, attribute: '桩函数（返回值）' };
        }
        exportTestCase.input[newStubName] = stubTimeValue;
      });
    });
    // 处理函数返回值
    const returnVariableArray = [];
    if (returnVal) {
      const newResFuncName = `[R]${functionName}`;
      this.buildVariableType(
        returnVariableArray,
        newResFuncName,
        returnVal,
        types[fileId],
        'return'
      );
    }
    // 处理输入变量
    const inputVariableArray = [];
    const variablesInputGlobals = variables.variables.global || {};
    Object.keys(inputGlobal).forEach((globalName) => {
      if (!variablesInputGlobals[globalName]) {
        return;
      }
      const { newName, variableFileId } = this.handleIntegrationVariableFile(fileId, globalName, globalVariablesData);
      this.buildVariableType(
        inputVariableArray,
        newName,
        inputGlobal[globalName],
        types[variableFileId],
        '全局变量'
      );
    });
    const variablesInputParams = variables.variables.params || {};
    Object.keys(inputParams).forEach((paramName) => {
      if (!variablesInputParams[paramName]) {
        return;
      }
      const { newName, variableFileId } = this.handleIntegrationVariableFile(fileId, paramName, paramVariablesData);
      this.buildVariableType(
        inputVariableArray,
        newName,
        inputParams[paramName],
        types[variableFileId],
        '一般形参'
      );
    });
    // 处理输出值
    const outputVariableArray = [];
    const variablesOutputGlobals = variables.output.global || {};
    Object.keys(outputGlobal).forEach((globalName) => {
      if (!variablesOutputGlobals[globalName]) {
        return;
      }
      const { newName, variableFileId } = this.handleIntegrationVariableFile(fileId, globalName, globalOutputData);
      this.buildVariableType(
        outputVariableArray,
        newName,
        outputGlobal[globalName],
        types[variableFileId],
        '全局变量'
      );
    });
    const variablesOutputParams = variables.output.params || {};
    Object.keys(outputParams).forEach((paramName) => {
      if (!variablesOutputParams[paramName]) {
        return;
      }
      const variableFileId = fileId.toString();
      this.buildVariableType(
        outputVariableArray,
        paramName,
        outputParams[paramName],
        types[variableFileId],
        '一般形参'
      );
    });
    // 组合表格结构数据
    [returnVariableArray, outputVariableArray, inputVariableArray].forEach((variableArray) => {
      variableArray.forEach((variable) => {
        if (variable.isUsed || funcDisplaySetting) {
          const newVariable = {
            name: variable.name,
            type: variable.type,
            isUsed: variable.isUsed,
            attribute: variable.attribute,
          };
          if (variableArray === inputVariableArray) {
            tableHeader.input[variable.name] = newVariable;
            exportTestCase.input[variable.name] = variable.value;
          } else {
            tableHeader.output[variable.name] = newVariable;
            exportTestCase.output[variable.name] = variable.value;
          }
        }
      });
    });
    testCases.push(exportTestCase);
  }

  /**
   * 导出单个函数的测试用例
   * @param {String} testsPath 测试用例路径
   * @param {Object} typeSystem 类型系统
   * @param {Array} functions 函数信息列表
   * @returns {Promise}
   */
  async exportFunctionTestCase(testsPath, typeSystem, func) {
    const testCaseStructuralData = {
      tableHeader: {
        input: {},
        output: {},
        userDefinedFields: this.exportUserDefinedFields,
      },
      testCases: [],
    };
    await common.complementByProjectVersionId(this.versionId, this.version, this.project, this.creator);
    await fs.ensureDir(path.dirname(testsPath));
    const [testFieldConfig] = await testModel.getTestFieldConfig(func.functionId, this.version, ['displaySetting']);
    const funcDisplaySetting = testFieldConfig && testFieldConfig.displaySetting ? parseInt(testFieldConfig.displaySetting, 10) : 0;
    let newFunctionName = func.functionName;
    // 去掉C++函数对应的函数名输入参数和（）
    if (newFunctionName.indexOf('(') !== -1) {
      [newFunctionName] = func.functionName.split('(');
    }
    testCaseStructuralData.functionName = newFunctionName;
    testCaseStructuralData.tableHeader.input.CaseID = {};
    testCaseStructuralData.tableHeader.input.ReqID = {};
    testCaseStructuralData.tableHeader.input.LoopCnt = {};
    await Promise.all(func.tests.map(async (testCase) => {
      testCaseStructuralData.fileId = testCase.fileId;
      testCase = testModel.getJson(testCase, this.i18n);
      let testTransformer;
      if (this.isIntegrationTest()) {
        testTransformer = new IntegrationTestCaseTransformer(testCase,
          func.variables, typeSystem);
      } else {
        testTransformer = new TestCaseTransformer(testCase, func.variables, typeSystem);
      }
      testTransformer.transform();
      const types = typeSystem.fileId
        ? { [typeSystem.fileId.toString()]: typeSystem.types } : typeSystem;
      await testModel.autoAddCaseId(testCase.testId);
      const group = await testGroupModel.getGroupByTestCaseId(testCase.testId);
      testCase.sortNumber = testCase.number;
      if (group) {
        testCase.groupNumber = group.number;
        const groupIndex = group.children.findIndex((e) => e.id.toString() === testCase.testId.toString());
        testCase.sortNumber = testCase.groupNumber * 10000 + groupIndex;
      }
      this.buildTestCaseExcelData(testCaseStructuralData, testCase, func.variables, types, testTransformer, funcDisplaySetting);
    }));
    const excel = new Excel();
    testCaseStructuralData.testCases = testCaseStructuralData.testCases.sort((a, b) => (a.sortNumber > b.sortNumber ? 1 : -1));
    const { testCases, functionName, tableHeader } = testCaseStructuralData;
    excel.addSheet(functionName);
    const attributes = ['全局变量', '一般形参', '桩函数（返回值）'];
    const funcColumns = [];
    const funcVariables = [{ name: 'LoopCnt', type: 'int', attribute: '' }];
    let ioIndex = 0;
    let endIndex = 0;
    let desStartIndex = 0;
    let desEndIndex = 0;
    // 处理输入变量列header
    Object.keys(tableHeader.input).forEach((key) => {
      const value = tableHeader.input[key];
      funcColumns.push({
        header: key,
        key,
        width: key.length > 10 ? key.length + 4 : 14,
        style: {
          alignment: { vertical: 'middle', horizontal: 'center' },
        },
      });
      if (attributes.includes(value.attribute)) {
        funcVariables.push(value);
      }
    });
    funcColumns.push({
      header: 'I/O',
      key: 'I/O',
      width: 4,
      style: {
        alignment: { vertical: 'middle', horizontal: 'center' },
      },
    });
    ioIndex = funcColumns.length;
    funcVariables.push({ name: 'I/O', type: '', attribute: '' });
    // 处理输出变量列header
    Object.keys(tableHeader.output).forEach((key) => {
      const value = tableHeader.output[key];
      funcColumns.push({
        header: key,
        key,
        width: key.length > 10 ? key.length + 4 : 14,
        style: {
          alignment: { vertical: 'middle', horizontal: 'center' },
        },
      });
      if (attributes.includes(value.attribute)) {
        funcVariables.push(value);
      }
    });
    funcColumns.push({
      header: 'END',
      key: 'END',
      width: 4,
      style: {
        alignment: { vertical: 'middle', horizontal: 'center' },
      },
    });
    endIndex = funcColumns.length;
    funcVariables.push({ name: 'END', type: '', attribute: '' });
    desStartIndex = funcColumns.length;
    // 处理自定义字段header
    this.exportUserDefinedFields.forEach((name) => {
      let width = name.length > 10 ? name.length + 4 : 14;
      if (name === '用例说明') {
        width = 54;
      }
      funcColumns.push({
        header: name,
        key: name,
        width,
        style: {
          alignment: { vertical: 'middle', horizontal: 'center' },
        },
      });
    });
    desEndIndex = funcColumns.length;
    funcColumns.push({
      header: '所属编组',
      key: '所属编组',
      width: 30,
      style: {
        alignment: { vertical: 'middle', horizontal: 'center' },
      },
    });
    const funcRowData = [];
    for (let i = 0, len = testCases.length; i < len; i += 1) {
      const testCase = testCases[i];
      const {
        input, output, userDefinedFields, groupNumber,
      } = testCase;
      const rowData = [];
      // 填充输入数据
      for (let j = 0; j < ioIndex; j += 1) {
        rowData.push(
          funcColumns[j] && input[funcColumns[j].key] ? input[funcColumns[j].key] : ''
        );
      }
      // 填充输出数据
      for (let k = ioIndex; k < endIndex; k += 1) {
        rowData.push(
          funcColumns[k] && output[funcColumns[k].key] ? output[funcColumns[k].key] : ''
        );
      }
      // 填充自定义字段数据
      const len = userDefinedFields.length;
      for (let k2 = 0; k2 < len; k2 += 1) {
        rowData[desStartIndex + k2] = userDefinedFields[k2];
      }
      // 填充用例所属编组编号数据
      rowData[desStartIndex + len] = groupNumber || '';
      funcRowData.push(rowData);
    }

    excel.addRows(funcColumns, funcRowData);
    // 设置首行个单元格样式
    excel.setRow(1, { height: 30 });
    const headerRow = excel.getRow(1);
    for (let i = 0, len = funcColumns.length; i < len; i += 1) {
      const cellNumber = i + 1;
      const fill = {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: '538DD5' },
      };
      if (['I/O', 'END'].includes(funcColumns[i].key)) {
        fill.fgColor.argb = 'FFC000';
      }
      if (i === desEndIndex - 1) {
        fill.fgColor.argb = 'C4D79B';
      }
      if (cellNumber > ioIndex && cellNumber < endIndex) {
        fill.fgColor.argb = '595959';
      }
      if (cellNumber > desStartIndex && cellNumber < desEndIndex) {
        fill.fgColor.argb = '808080';
      }
      headerRow.getCell(cellNumber).fill = fill;
      headerRow.getCell(cellNumber).font = { size: 11, color: { argb: 'FFFFFF' } };
      headerRow.getCell(cellNumber).border = {
        top: { style: 'thin' },
        left: { style: 'thin' },
        bottom: { style: 'thin' },
        right: { style: 'thin' },
      };
    }
    // 处理自定义字段单元格dataValidation
    for (let i = 0, len = testCases.length; i < len; i += 1) {
      const dataRow = excel.getRow(i + 2);
      for (let j = desStartIndex + 1; j < desEndIndex; j += 1) {
        dataRow.getCell(j).dataValidation = {
          type: 'list',
          formulae: ['"×,√"'],
          operator: 'notEqual',
          showErrorMessage: true,
          errorStyle: 'error',
          errorTitle: '值校验错误',
          allowBlank: true,
          error: '只能填写 × 或者 √。',
        };
      }
    }
    // 变量信息信息表导出
    excel.addSheet('变量信息映射表');
    excel.setCell('A1', { value: '变量名称' });
    excel.setCell('B1', { value: '变量类型' });
    excel.setCell('C1', { value: '变量属性' });
    excel.setColumn('A', { width: 30 });
    excel.setColumn('B', { width: 15 });
    excel.setColumn('C', { width: 20 });
    for (let i = 0, len = funcVariables.length; i < len; i += 1) {
      const variable = funcVariables[i];
      const rowNumber = i + 2;
      excel.setCell(`A${rowNumber}`, { value: variable.name });
      if (!['END', 'I/O'].includes(variable.name)) {
        excel.setCell(`B${rowNumber}`, { value: variable.type });
        excel.setCell(`C${rowNumber}`, {
          value: variable.attribute,
          dataValidation: {
            type: 'list',
            formulae: ['"全局变量,一般形参,桩函数（返回值）"'],
            operator: 'notEqual',
            showErrorMessage: true,
            errorStyle: 'error',
            errorTitle: '值校验错误',
            error: '只能填写全局变量,一般形参,或者桩函数（返回值）。',
          },
        });
      }
    }

    return excel.writeFile(utility.handleWindwowFileNameSpecialCharacters(testsPath));
  }
}

module.exports = ExcelTestCasesExporterOf614;
